import RawAsyncValidator from 'async-validator';
import * as React from 'react';
import type { InternalNamePath, StoreValue } from '../interface';
import type {
  InternalValidateOptions,
  RuleError,
  RuleObject,
} from '../interface/ValidateType';
import { defaultValidateMessages } from './messages';

// Remove incorrect original ts define
const AsyncValidator: any = RawAsyncValidator;

/**
 * Replace with template.
 *   `I'm ${name}` + { name: 'bamboo' } = I'm bamboo
 */
function replaceMessage(template: string, kv: Record<string, string>): string {
  return template.replace(/\$\{\w+\}/g, (str: string) => {
    const key = str.slice(2, -1);
    return kv[key];
  });
}

const CODE_LOGIC_ERROR = 'CODE_LOGIC_ERROR';

async function validateRule(
  name: string,
  value: StoreValue,
  rule: RuleObject,
  options: InternalValidateOptions,
  messageVariables?: Record<string, string>,
): Promise<string[]> {
  const cloneRule = { ...rule };

  // Bug of `async-validator`
  // https://github.com/react-component/field-form/issues/316
  // https://github.com/react-component/field-form/issues/313
  // delete (cloneRule as any).ruleIndex;

  // https://github.com/ant-design/ant-design/issues/40497#issuecomment-1422282378
  AsyncValidator.warning = () => void 0;
  // 自定义校验函数，不走validator.validate
  if (cloneRule.validator) {
    const resV = cloneRule.validator(rule, value) as Promise<string>;
    resV
      .then(() => {
        // console.log('------------- -1 validator--then --------')
        return Promise.resolve([]);
      })
      .catch((error: string) => {
        // console.log('------------- -1.1 validator--catch--------')
        return Promise.resolve([error]);
      });
  }

  // We should special handle array validate
  let subRuleField: RuleObject = {};
  if (cloneRule && cloneRule.type === 'array' && cloneRule.defaultField) {
    subRuleField = cloneRule.defaultField;
    delete cloneRule.defaultField;
  }

  const validator = new AsyncValidator({
    [name]: [cloneRule],
  });

  const messages = Object.assign(
    defaultValidateMessages,
    options.validateMessages,
  );
  validator.messages(messages);

  let result = [];
  try {
    await Promise.resolve(
      validator.validate({ [name]: value }, { ...options }),
    );
  } catch (errObj) {
    //校验不通过
    if ((errObj as any).errors) {
      result = (errObj as any).errors.map(
        ({ message }: { message: any }, index: number) => {
          const mergedMessage =
            message === CODE_LOGIC_ERROR ? messages.default : message;

          return React.isValidElement(mergedMessage)
            ? // Wrap ReactNode with `key`
              React.cloneElement(mergedMessage, { key: `error_${index}` })
            : mergedMessage;
        },
      );
    }
  }
  // Filed.rule={[{required:true,message:'请输入2-6个字'}]} -- result:[请输入2-6个字]
  if (!result.length && subRuleField && Object.keys(subRuleField).length > 0) {
    const subResults: string[][] = await Promise.all(
      (value as StoreValue[]).map((subValue: StoreValue, i: number) =>
        validateRule(
          `${name}.${i}`,
          subValue,
          subRuleField,
          options,
          messageVariables,
        ),
      ),
    );

    return subResults.reduce((prev, errors) => [...prev, ...errors], []);
  }

  // Replace message with variables
  const kv = {
    ...(rule as Record<string, string | number>),
    name,
    enum: (rule.enum || []).join(', '),
    ...messageVariables,
  };

  const fillVariableResult = result.map((error: any) => {
    if (typeof error === 'string') {
      return replaceMessage(error, kv);
    }
    return error;
  });
  return fillVariableResult;
}

/**
 * We use `async-validator` to validate the value.
 * But only check one value in a time to avoid namePath validate issue.
 */
/**
 *
 *
 * @export
 * @param {InternalNamePath} namePath
 * @param {StoreValue} value Field.props.value
 * @param {RuleObject[]} rules Field.props.rules
 * @param {InternalValidateOptions} options { triggerName?: string,validateMessages?: ValidateMessages,validateOnly?:boolean,recursive?:boolean}
 * @param {(boolean | 'parallel')} validateFirst
 * @param {Record<string, string>} [messageVariables] Field.props.messageVariables
 * @returns
 *
 */
export function validateRulesFn(
  namePath: InternalNamePath,
  value: StoreValue,
  rules: RuleObject[],
  options: InternalValidateOptions,
  validateFirst: boolean | 'parallel',
  messageVariables?: Record<string, string>,
) {
  const name = namePath.join('.'); //['users',0,'name'] -- users.0.name

  let summaryPromise: Promise<RuleError[]>;

  if (validateFirst === true) {
    summaryPromise = new Promise(async (resolve, reject) => {
      for (let i = 0; i < rules.length; i += 1) {
        const rule = rules[i];
        const errors = await validateRule(
          name,
          value,
          rule,
          options,
          messageVariables,
        );
        if (errors.length) {
          reject([{ errors, rule }]);
          return;
        }
      }
      resolve([]);
    });
  } else {
    summaryPromise = new Promise(async (resolve, reject) => {
      const errosRes: RuleError[] = [];
      for (let i = 0; i < rules.length; i += 1) {
        const rule = rules[i];
        try {
          const errors = await validateRule(
            name,
            value,
            rule,
            options,
            messageVariables,
          );
          if (errors.length) {
            errosRes.push({ errors, rule: rule || {} });
          }
        } catch (error) {
          // console.log('----2.0--validateRule--catch--error----:',error);
          // errosRes.push({rule:{},errors:[error]})
        }
      }
      // console.log('-----2.0---------222222222--errosRes--------:',errosRes)
      if (errosRes.length > 0) {
        reject(errosRes);
      } else {
        resolve([]);
      }
    });
  }

  return summaryPromise;
}
